package core

import (
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/params"
	"github.com/holiman/uint256"
)

//这里实现的是受BatchStorage的设计导致有较大影响的operation的实现
func opBalance(pc *uint64, Interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	//调用BatchStorage.GetBalance拿
	slot := scope.Stack.Peek()
	address := common.Address(slot.Bytes20())
	balance := Interpreter.evm.BatchStorage.GetBalance(address)
	slot.Set(balance)
	return nil, nil
}

func opBlockHash(pc *uint64, Interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	//调用BatchStorage.GetBlockHash拿
	num := scope.Stack.Peek()
	num64, overflow := num.Uint64WithOverflow()
	if overflow {
		num.Clear()
		return nil, nil
	}
	var upper, lower uint64
	upper = Interpreter.evm.ConstantContext.BlockContext.Number.Uint64()
	if upper < 257 {
		lower = 0
	} else {
		lower = upper - 256
	}
	if num64 >= lower && num64 < upper {
		num.SetBytes(Interpreter.evm.BatchStorage.GetBlockHash(num64).Bytes())
	} else {
		num.Clear()
	}
	return nil, nil
}

func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	//该指令的功能是从storage中获取某一个address的CodeSize
	slot := scope.Stack.Peek()
	slot.SetUint64(uint64(interpreter.evm.BatchStorage.GetCodeSize(slot.Bytes20())))
	return nil, nil
}

func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	var (
		stack      = scope.Stack
		a          = stack.Pop()
		memOffset  = stack.Pop()
		codeOffset = stack.Pop()
		length     = stack.Pop()
	)
	uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
	if overflow {
		uint64CodeOffset = 0xffffffffffffffff
	}
	addr := common.Address(a.Bytes20())
	code := interpreter.evm.BatchStorage.GetCode(addr)
	codeCopy := getData(code, uint64CodeOffset, length.Uint64())
	scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)

	return nil, nil
}

func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	loc := scope.Stack.Peek()
	hash := common.Hash(loc.Bytes32())
	address := scope.CallContract.Address()
	val := interpreter.evm.BatchStorage.GetState(address, hash)
	loc.SetBytes(val.Bytes())
	return nil, nil
}

func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	if interpreter.readOnly {
		return nil, ErrWriteProtection
	}
	loc := scope.Stack.Pop()
	val := scope.Stack.Pop()

	//注意对于Call和CallCode/delegateCall来说address是不同的
	address := scope.CallContract.Address()

	interpreter.evm.BatchStorage.SetState(address, loc.Bytes32(), val.Bytes32())

	return nil, nil
}

func makeLog(size int) executeFunc {
	return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
		if interpreter.readOnly {
			return nil, ErrWriteProtection
		}
		topics := make([]common.Hash, size)
		mStart := scope.Stack.Pop()
		mSize := scope.Stack.Pop()

		for i := 0; i < size; i++ {
			addr := scope.Stack.Pop()
			topics[i] = addr.Bytes32()
		}
		data := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
		address := scope.CallContract.Address()
		blockNumber := interpreter.evm.ConstantContext.BlockContext.Number.Uint64()
		log := &types.Log{
			Address:     address,
			Topics:      topics,
			Data:        data,
			BlockNumber: blockNumber,
		}

		interpreter.evm.BatchStorage.AddLog(address, log)

		return nil, nil
	}
}

func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	if interpreter.readOnly {
		return nil, ErrWriteProtection
	}
	beneficiary := scope.Stack.Pop()
	targetAddress := scope.CallContract.Address()
	balance := interpreter.evm.BatchStorage.GetBalance(targetAddress)

	//会将销毁的contract中保存的value全部发送给beneficiary
	interpreter.evm.BatchStorage.BalanceModify(beneficiary.Bytes20(), balance, false)

	interpreter.evm.BatchStorage.Destruct(targetAddress)
	return nil, nil
}

func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	stack := scope.Stack
	//第一个参数是Gas
	temp := stack.Pop()
	//需要注意的是在EIP150之后每一次Call可以使用的Gas为上一Call的63/64
	gas := interpreter.evm.CallGasTemp
	// Pop other call parameters.
	addr, value, inOffset, inSize := stack.Pop(), stack.Pop(), stack.Pop(), stack.Pop()

	retOffset, retSize := stack.Pop(), stack.Pop()

	toAddr := common.Address(addr.Bytes20())

	// Get the Input from the memory.
	input := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	if interpreter.readOnly && !value.IsZero() {
		return nil, ErrWriteProtection
	}

	if !value.IsZero() {
		//Free gas given at the begining of call
		gas += params.CallStipend
	}

	ret, returnGas, err := interpreter.evm.Call(scope.CallContract, toAddr, input, gas, &value)

	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}

	stack.Push(&temp)

	if err == nil || err == ErrExecutionReverted {
		ret = common.CopyBytes(ret)
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.CallContract.Gas += returnGas

	interpreter.returnData = ret

	return nil, nil
}

func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	stack := scope.Stack
	//第一个参数是Gas
	temp := stack.Pop()
	//需要注意的是在EIP150之后每一次Call可以使用的Gas为上一Call的63/64
	gas := interpreter.evm.CallGasTemp
	// Pop other call parameters.
	addr, value, inOffset, inSize := stack.Pop(), stack.Pop(), stack.Pop(), stack.Pop()

	retOffset, retSize := stack.Pop(), stack.Pop()

	toAddr := common.Address(addr.Bytes20())

	// Get the Input from the memory.
	input := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	if !value.IsZero() {
		gas += params.CallStipend
	}

	//需要注意的是调用CallCode的时候value用的是0而不是从栈中取出的value
	ret, returnGas, err := interpreter.evm.CallCode(scope.CallContract, toAddr, input, gas, uint256.NewInt(0))

	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}
	stack.Push(&temp)

	if err == nil || err == ErrExecutionReverted {
		ret = common.CopyBytes(ret)
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.CallContract.Gas += returnGas

	interpreter.returnData = ret
	return nil, nil
}

func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	if interpreter.readOnly {
		return nil, ErrWriteProtection
	}
	var (
		value        = scope.Stack.Pop()
		offset, size = scope.Stack.Pop(), scope.Stack.Pop()
		input        = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
		gas          = scope.CallContract.Gas
	)

	//EIP150是为了防止一笔交易会创建太多的账户，所以每一次调用create的时候都会将gas设置为父gas的63/64
	if interpreter.evm.ChainRules.IsEIP150 {
		gas -= gas / 64
	}

	stackvalue := size

	scope.CallContract.UseGas(gas)

	res, addr, returnGas, suberr := interpreter.evm.Create(scope.CallContract, input, gas, &value)

	if interpreter.evm.ChainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas {
		stackvalue.Clear()
	} else if suberr != nil && suberr != ErrCodeStoreOutOfGas {
		stackvalue.Clear()
	} else {
		stackvalue.SetBytes(addr.Bytes())
	}
	scope.Stack.Push(&stackvalue)
	scope.CallContract.Gas += returnGas

	if suberr == ErrExecutionReverted {
		interpreter.returnData = res // set REVERT data to return data buffer
		return res, nil
	}
	interpreter.returnData = nil
	return nil, nil
}

func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	if interpreter.readOnly {
		return nil, ErrWriteProtection
	}
	var (
		endowment    = scope.Stack.Pop()
		offset, size = scope.Stack.Pop(), scope.Stack.Pop()
		salt         = scope.Stack.Pop()
		input        = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
		gas          = scope.CallContract.Gas
	)

	// Apply EIP150
	if interpreter.evm.ChainRules.IsEIP150 {
		gas -= gas / 64
	}
	// reuse size int for stackvalue
	stackvalue := size
	res, addr, returnGas, suberr := interpreter.evm.Create2(scope.CallContract, input, gas, &endowment, &salt)

	if suberr != nil {
		stackvalue.Clear()
	} else {
		stackvalue.SetBytes(addr.Bytes())
	}
	scope.Stack.Push(&stackvalue)
	scope.CallContract.Gas += returnGas

	if suberr == ErrExecutionReverted {
		interpreter.returnData = res
		return res, nil
	}
	interpreter.returnData = nil
	return nil, nil
}
